package org.examcity.util.beanconverter;

import java.util.HashMap;
import java.util.Map;

import org.examcity.util.ReflectionUtil;

public class ConverterManagerImpl implements ConverterManager {

	private Map<RuleKey, Rule> ruleMap = new HashMap<RuleKey, Rule>();
	private Converter defaultConverter;
	private Factory defaultFactory;

	public void init() {
		defaultConverter = new DefaultConverter();
		defaultFactory = new DefaultFactory();
	}

	public Factory getDefaultFactory() {
		return defaultFactory;
	}

	public void setDefaultFactory(Factory valueFactory) {
		this.defaultFactory = valueFactory;
	}

	public void setDefaultConverter(Converter defaultBeanConverter) {
		this.defaultConverter = defaultBeanConverter;
	}

	public Converter getDefaultConverter() {
		return defaultConverter;
	}

	public Rule findRule(Class<?> sourceClass, Class<?> targetClass) {
		return ruleMap.get(new RuleKey(sourceClass, targetClass));
	}

	public void addRule(Rule rule) {
		RuleKey key = new RuleKey(rule.getClassA(), rule.getClassB());
		if (ruleMap.containsKey(key)) {
			throw new ConverterException("Rule conflict");
		}
		ruleMap.put(key, rule);
	}

	@SuppressWarnings("unchecked")
	public <T> T convert(Object source, Class<T> targetClass) throws ConverterException {
		try {
			return (T) defaultConverter.convert(new ConverterContext(this), ReflectionUtil
					.getClass(source), source, targetClass);
		} catch (Exception e) {
			throw new ConverterException("convertion failed: " + e.getMessage(), e);
		}
	}

	private static class RuleKey {

		private final String fromClassName;
		private final String toClassName;

		public RuleKey(Class<?> fromClass, Class<?> toClass) {
			this.fromClassName = fromClass.getName();
			this.toClassName = toClass.getName();
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((fromClassName == null) ? 0 : fromClassName.hashCode());
			result = prime * result + ((toClassName == null) ? 0 : toClassName.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			RuleKey other = (RuleKey) obj;
			if (fromClassName == null) {
				if (other.fromClassName != null)
					return false;
			} else if (!fromClassName.equals(other.fromClassName))
				return false;
			if (toClassName == null) {
				if (other.toClassName != null)
					return false;
			} else if (!toClassName.equals(other.toClassName))
				return false;
			return true;
		}

	}

}
